﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.IO
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;

    /// <summary>Provides common functionality for streams that use buffers to read from and write to an underlying
    /// stream.</summary>
    /// <remarks>
    /// <para>A concrete subclass should implement the abstract <see cref="Stream.Read(byte[], int, int)"/> method in
    /// terms of the <see cref="M:ReadBuffer.Buffer"/>, <see cref="M:ReadBuffer.Offset"/>,
    /// <see cref="M:ReadBuffer.Count"/> and <see cref="M:ReadBuffer.FillBuffer"/> members of the
    /// <see cref="M:ReadBuffer"/> object. The abstract <see cref="Stream.Write(byte[], int, int)"/> method should be
    /// implemented in terms of the <see cref="M:WriteBuffer.Buffer"/>, <see cref="M:WriteBuffer.Offset"/> and
    /// <see cref="M:WriteBuffer.Flush"/> members of the <see cref="M:WriteBuffer"/> object.</para>
    /// </remarks>
    /// <threadsafety static="true" instance="false"/>
    public abstract class BufferStream : NonSeekableStream
    {
        private Stream stream;
        private readonly int bufferSize;
        private ReadBuffer readBuffer;
        private WriteBuffer writeBuffer;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="Stream.CanRead"/>.</summary>
        public sealed override bool CanRead
        {
            get { return (this.readBuffer != null) || ((this.stream != null) && this.stream.CanRead); }
        }

        /// <summary>See <see cref="Stream.CanWrite"/>.</summary>
        public override bool CanWrite
        {
            get { return (this.writeBuffer != null) || ((this.stream != null) && this.stream.CanWrite); }
        }

        /// <summary>See <see cref="Stream.Flush"/>.</summary>
        /// <remarks>Calls <see cref="M:WriteBuffer.Flush"/>.</remarks>
        public override void Flush()
        {
            this.AssertNotDisposed();

            if (this.writeBuffer != null)
            {
                this.writeBuffer.Flush();
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="BufferStream"/> class.</summary>
        /// <param name="stream">The underlying stream.</param>
        /// <param name="bufferSize">The size of the internal read and write buffers in bytes.</param>
        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="bufferSize"/> is less than or equal to 0.
        /// </exception>
        internal BufferStream(Stream stream, int bufferSize)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (bufferSize <= 0)
            {
                throw new ArgumentOutOfRangeException("bufferSize", ExceptionMessages.PositiveNumberRequired);
            }

            this.stream = stream;
            this.bufferSize = bufferSize;
        }

        /// <summary>Initializes a new instance of the <see cref="BufferStream"/> class.</summary>
        internal BufferStream(ReadBuffer readBuffer, WriteBuffer writeBuffer)
        {
            this.readBuffer = readBuffer;
            this.writeBuffer = writeBuffer;
        }

        /// <summary>Gets a reference to the read buffer.</summary>
        internal ReadBuffer ReadBuffer
        {
            get
            {
                if (this.readBuffer == null)
                {
                    if ((this.stream == null) || !this.stream.CanRead)
                    {
                        throw new NotSupportedException("Stream does not support reading.");
                    }

                    this.readBuffer = new ReadBuffer(this.stream.Read, this.bufferSize);
                }

                return this.readBuffer;
            }
        }

        /// <summary>Gets a reference to the write buffer.</summary>
        internal WriteBuffer WriteBuffer
        {
            get
            {
                if (this.writeBuffer == null)
                {
                    if ((this.stream == null) || !this.stream.CanWrite)
                    {
                        throw new NotSupportedException("Stream does not support writing.");
                    }

                    this.writeBuffer = new WriteBuffer(this.stream.Write, this.stream.Flush, this.bufferSize);
                }

                return this.writeBuffer;
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="NonSeekableStream.IsDisposed"/>.</summary>
        protected sealed override bool IsDisposed
        {
            get { return (this.readBuffer == null) && (this.writeBuffer == null) && (this.stream == null); }
        }

        /// <summary>Flushes the write buffer and then disposes the underlying stream.</summary>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Dispose() must never throw.")]
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing && !this.IsDisposed)
                {
                    this.Flush();

                    if (this.stream != null)
                    {
                        this.stream.Dispose();
                    }
                }
            }
            catch
            {
            }
            finally
            {
                this.readBuffer = null;
                this.writeBuffer = null;
                this.stream = null;
                base.Dispose(disposing);
            }
        }
    }
}
